using System;
using System.Collections;
using System.Text;
using ASOGS.Patterns;
using Reflector.CodeModel;

namespace Doubler.Helpers.Graph
{
    public sealed class CallGraphBuilder
    {
        
#region Fields

        private DoubleDispatchReflector _StatementVisitor;
        private DoubleDispatchReflector _ExpressionVistor;
        private CallGraph _Graph;
        private ITypeReference _OwningTypeOfMethodToExamine;
        private IMethodDeclaration _MethodToExamine;
        private Logging.LoggerBase _Logger;

#endregion
        
#region Properties

        public Logging.LoggerBase Logger
        {
            get
            {
                if (_Logger == null)
                    _Logger = Logging.LogClassFactory.Logger;
                return _Logger;
            }
            set
            {
                _Logger = value;
            }
        }

        public ITypeReference OwningTypeOfMethodToExamine
        {
            get
            {
                return _OwningTypeOfMethodToExamine;
            }
            set
            {
                _OwningTypeOfMethodToExamine = value;
            }
        }

        public IMethodDeclaration MethodToExamine
        {
            get
            {
                return _MethodToExamine;
            }
            set
            {
                _MethodToExamine = value;
            }
        }

        private CallGraph Graph
        {
            get
            {
                if (_Graph == null)
                    _Graph = new CallGraph(this.MethodToExamine);
                return _Graph;
            }
            set
            {
                _Graph = value;
            }
        }

        private DoubleDispatchReflector StatementVisitor
        {
            get
            {
                if (_StatementVisitor == null)
                    _StatementVisitor = new DoubleDispatchReflector(this, "ProcessStatement");
                return _StatementVisitor;
            }
            set
            {
                _StatementVisitor = value;
            }
        }
        private DoubleDispatchReflector ExpressionVistor
        {
            get
            {
                if (_ExpressionVistor == null)
                    _ExpressionVistor = new DoubleDispatchReflector(this, "ProcessExpression");
                return _ExpressionVistor;
            }
            set
            {
                _ExpressionVistor = value;
            }
        }

#endregion
        
#region Constructors

        public CallGraphBuilder(IMethodDeclaration methodToExamine)
        {
            _OwningTypeOfMethodToExamine = (Reflector.CodeModel.ITypeReference)methodToExamine.DeclaringType;
            _MethodToExamine = methodToExamine;
        }

#endregion

#region ConstructGraph

        public CallGraph ConstructGraph()
        {
            foreach (IStatement Statement in ((IBlockStatement)this.MethodToExamine.Body).Statements)
            {
                this.Accept(Statement);
            }
            return this.Graph;
        }

#endregion
        
#region Accept

        public void Accept(IStatement statement)
        {
            try
            {
                this.StatementVisitor.Invoke(statement);
            }
            catch (Exception ex)
            {
                this.Logger.Log(String.Format("Error while visiting a statmement of type(s) {0}: {1}", this.TypeBuilder(statement), ex.InnerException.ToString()));
            }
        }

        public void Accept(IExpression expression)
        {
            try
            {
                this.ExpressionVistor.Invoke(expression);
            }
            catch (InvalidOperationException ex)
            {
                if (ex.InnerException != null)
                {
                    this.Logger.Log(String.Format("Error while visiting an expression of type(s) {0}: {1}", this.TypeBuilder(expression), ex.InnerException.ToString()));
                }
                else
                {
                    this.Logger.Log(String.Format("Could not find visitor for expression of type(s) {0}", this.TypeBuilder(expression)));
                }
            }
        }

        public void Accept(IExpression expression, Object arg)
        {
            try
            {
                Object[] Args = new Object[2] { expression, arg };
                this.ExpressionVistor.Invoke(Args);
            }
            catch (InvalidOperationException ex)
            {
                if (ex.InnerException != null)
                {
                    this.Logger.Log(String.Format("Error while visiting an expression of type(s) {0} and an object of type(s) {1}: {2}", this.TypeBuilder(expression), this.TypeBuilder(arg), ex.InnerException.ToString()));
                }
                else
                {
                    this.Logger.Log(String.Format("Could not find visitor for expression of type(s) {0} and an object of type(s) {1}", this.TypeBuilder(expression), this.TypeBuilder(arg)));
                }
            }
        }

        public void Accept(IExpression expression, Object arg1, Object arg2)
        {
            try
            {
                Object[] Args = new Object[3] { expression, arg1, arg2 };
                this.ExpressionVistor.Invoke(Args);
            }
            catch (InvalidOperationException ex)
            {
                if (ex.InnerException != null)
                {
                    this.Logger.Log(String.Format("Error while visiting an expression of type(s) {0} and an object of type(s) {1} and an object of type(s) {2}: {3}", this.TypeBuilder(expression), this.TypeBuilder(arg1), this.TypeBuilder(arg2), ex.InnerException.ToString()));
                }
                else
                {
                    this.Logger.Log(String.Format("Could not find visitor for expression of type(s) {0} and an object of type(s) {1} and an object of type(s) {2}", this.TypeBuilder(expression), this.TypeBuilder(arg1), this.TypeBuilder(arg2)));
                }
            }
        }

#endregion
        
#region Statement Processing

        public void ProcessStatement(IAttachEventStatement attachEventStatement)
        {
            //nothing
        }

        public void ProcessStatement(IBlockStatement blockStatement)
        {
            foreach (IStatement Statement in blockStatement.Statements)
            {
                this.Accept(Statement);
            }
        }

        public void ProcessStatement(IBreakStatement breakStatement)
        {
            //nothing
        }

        public void ProcessStatement(ICommentStatement commentStatement)
        {
            //nothing
        }

        public void ProcessStatement(IConditionStatement conditionStatement)
        {
            this.Accept(conditionStatement.Condition);
            this.Accept(conditionStatement.Then);
            this.Accept(conditionStatement.Else);
        }

        public void ProcessStatement(IContinueStatement continueStatement)
        {
            //nothing
        }

        public void ProcessStatement(IDoStatement doStatement)
        {
            this.Accept(doStatement.Body);
            this.Accept(doStatement.Condition);
        }

        public void ProcessStatement(IExpressionStatement expressionStatement)
        {
            this.Accept(expressionStatement.Expression);
        }

        public void ProcessStatement(IFixedStatement fixedStatement)
        {
            this.Accept(fixedStatement.Body);
            this.Accept(fixedStatement.Expression);
        }

        public void ProcessStatement(IForEachStatement forEachStatement)
        {
            this.Accept(forEachStatement.Body);
            this.Accept(forEachStatement.Expression);
        }

        public void ProcessStatement(IForStatement forStatement)
        {
            this.Accept(forStatement.Body);
            this.Accept(forStatement.Condition);
            this.Accept(forStatement.Increment);
            this.Accept(forStatement.Initializer);
        }

        public void ProcessStatement(IGotoStatement gotoStatement)
        {
            //nothing
        }

        public void ProcessStatement(ILabeledStatement labeledStatement)
        {
            this.Accept(labeledStatement.Statement);
        }

        public void ProcessStatement(ILockStatement lockStatement)
        {
            this.Accept(lockStatement.Expression);
        }

        public void ProcessStatement(IMemoryCopyStatement memoryCopyStatement)
        {
            this.Accept(memoryCopyStatement.Destination);
            this.Accept(memoryCopyStatement.Length);
            this.Accept(memoryCopyStatement.Source);
        }

        public void ProcessStatement(IMemoryInitializeStatement memoryInitializeStatement)
        {
            this.Accept(memoryInitializeStatement.Length);
            this.Accept(memoryInitializeStatement.Offset);
            this.Accept(memoryInitializeStatement.Value);
        }

        public void ProcessStatement(IMethodReturnStatement methodReturnStatement)
        {
            if (methodReturnStatement.Expression != null)
                this.Accept(methodReturnStatement.Expression);
        }

        public void ProcessStatement(IRemoveEventStatement removeEventStatement)
        {
            //nothing
        }

        public void ProcessStatement(ISwitchStatement switchStatement)
        {
            this.Accept(switchStatement.Expression);
            foreach (ISwitchCase SwitchCase in switchStatement.Cases)
            {
                this.Accept(SwitchCase.Body);
            }
        }

        public void ProcessStatement(IThrowExceptionStatement throwExceptionStatement)
        {
            this.Accept(throwExceptionStatement.Expression);
        }

        public void ProcessStatement(ITryCatchFinallyStatement tryCatchFinallyStatement)
        {
            this.Accept(tryCatchFinallyStatement.Try);
            this.Accept(tryCatchFinallyStatement.Finally);
            this.Accept(tryCatchFinallyStatement.Fault);
            foreach (ICatchClause CatchClause in tryCatchFinallyStatement.CatchClauses)
            {
                this.Accept(CatchClause.Body);
            }
        }

        public void ProcessStatement(IUsingStatement usingStatement)
        {
            this.Accept(usingStatement.Body);
            this.Accept(usingStatement.Expression);
        }

        public void ProcessStatement(IWhileStatement whileStatement)
        {
            this.Accept(whileStatement.Body);
            this.Accept(whileStatement.Condition);
        }

#endregion
        
#region Expression Processing

        public void ProcessExpression(IAddressDereferenceExpression addressDereferenceExpression)
        {
            this.Logger.Log(String.Format("Visiting an expression of type(s) {0}.", this.TypeBuilder(addressDereferenceExpression)));
        }

        public void ProcessExpression(IAddressOfExpression addressOfExpression)
        {
            this.Logger.Log(String.Format("Visiting an expression of type(s) {0}.", this.TypeBuilder(addressOfExpression)));            
        }

        public void ProcessExpression(IAddressOutExpression addressOutExpression)
        {
            this.Logger.Log(String.Format("Visiting an expression of type(s) {0}.", this.TypeBuilder(addressOutExpression)));
        }

        public void ProcessExpression(IArgumentListExpression argumentListExpression)
        {
            this.Logger.Log(String.Format("Visiting an expression of type(s) {0}.", this.TypeBuilder(argumentListExpression)));
        }

        public void ProcessExpression(IArrayCreateExpression arrayCreateExpression)
        {
            this.Logger.Log(String.Format("Visiting an expression of type(s) {0}.", this.TypeBuilder(arrayCreateExpression)));
        }

        public void ProcessExpression(IArrayIndexerExpression arrayIndexerExpression)
        {
            this.Logger.Log(String.Format("Visiting an expression of type(s) {0}.", this.TypeBuilder(arrayIndexerExpression)));
        }

        public void ProcessExpression(IAssignExpression assignExpression)
        {
            this.Accept(assignExpression.Expression);
            this.Accept(assignExpression.Target, assignExpression);
        }

        public void ProcessExpression(IBinaryExpression binaryExpression)
        {
            //nothing to do
        }

        public void ProcessExpression(ICanCastExpression canCastExpression)
        {
            this.Logger.Log(String.Format("Visiting an expression of type(s) {0}.", this.TypeBuilder(canCastExpression)));
        }

        public void ProcessExpression(ICastExpression castExpression)
        {
            this.Logger.Log(String.Format("Visiting an expression of type(s) {0}.", this.TypeBuilder(castExpression)));
        }

        public void ProcessExpression(IConditionExpression conditionExpression)
        {
            this.Logger.Log(String.Format("Visiting an expression of type(s) {0}.", this.TypeBuilder(conditionExpression)));
        }

        public void ProcessExpression(IDelegateCreateExpression delegateCreateExpression)
        {
            this.Logger.Log(String.Format("Visiting an expression of type(s) {0}.", this.TypeBuilder(delegateCreateExpression)));
        }

        public void ProcessExpression(IDelegateInvokeExpression delegateInvokeExpression)
        {
            this.Logger.Log(String.Format("Visiting an expression of type(s) {0}.", this.TypeBuilder(delegateInvokeExpression)));
        }

        public void ProcessExpression(IGenericDefaultExpression genericDefaultExpression)
        {
            this.Logger.Log(String.Format("Visiting an expression of type(s) {0}.", this.TypeBuilder(genericDefaultExpression)));
        }

        public void ProcessExpression(ILiteralExpression literalExpression)
        {
            //nothing to do
        }

        public void ProcessExpression(IMethodInvokeExpression methodInvokeExpression)
        {
            this.Accept(methodInvokeExpression.Method, methodInvokeExpression);
        }

        public void ProcessExpression(INullCoalescingExpression nullCoalescingExpression)
        {
            this.Logger.Log(String.Format("Visiting an expression of type(s) {0}.", this.TypeBuilder(nullCoalescingExpression)));
        }

        public void ProcessExpression(IObjectCreateExpression objectCreateExpression)
        {
            //nothing to do
        }

        public void ProcessExpression(IPropertyIndexerExpression propertyIndexerExpression)
        {
            this.Logger.Log(String.Format("Visiting an expression of type(s) {0}.", this.TypeBuilder(propertyIndexerExpression)));
        }

        public void ProcessExpression(ISizeOfExpression sizeOfExpression)
        {
            this.Logger.Log(String.Format("Visiting an expression of type(s) {0}.", this.TypeBuilder(sizeOfExpression)));
        }

        public void ProcessExpression(ISnippetExpression snippetExpression)
        {
            this.Logger.Log(String.Format("Visiting an expression of type(s) {0}.", this.TypeBuilder(snippetExpression)));
        }

        public void ProcessExpression(IStackAllocateExpression stackAllocateExpression)
        {
            this.Logger.Log(String.Format("Visiting an expression of type(s) {0}.", this.TypeBuilder(stackAllocateExpression)));
        }

        public void ProcessExpression(ITryCastExpression tryCastExpression)
        {
            this.Logger.Log(String.Format("Visiting an expression of type(s) {0}.", this.TypeBuilder(tryCastExpression)));
        }

        public void ProcessExpression(ITypeOfExpression typeOfExpression)
        {
            this.Logger.Log(String.Format("Visiting an expression of type(s) {0}.", this.TypeBuilder(typeOfExpression)));
        }

        public void ProcessExpression(IUnaryExpression unaryExpression)
        {
            this.Logger.Log(String.Format("Visiting an expression of type(s) {0}.", this.TypeBuilder(unaryExpression)));
        }

        public void ProcessExpression(IVariableDeclarationExpression variableDeclarationExpression)
        {
            this.Logger.Log(String.Format("Visiting an expression of type(s) {0}.", this.TypeBuilder(variableDeclarationExpression)));
        }

#endregion
        
#region Reference Expressions

        public void ProcessExpression(IVariableReferenceExpression variableReferenceExpression)
        {
            //nothing to do for something created and scoped locally.
        }

        public void ProcessExpression(IVariableReferenceExpression variableReferenceExpression, CallChain callChain)
        {
            if (!this.Graph.FromVariables.ContainsKey(variableReferenceExpression))
            {
                this.Graph.FromVariables.Add(variableReferenceExpression, new CallsFromVariable(variableReferenceExpression));
            }
            this.Graph.FromVariables[variableReferenceExpression].CallChains.Add(callChain);
        }
        
        public void ProcessExpression(IValueOfTypedReferenceExpression valueOfTypedReferenceExpression)
        {
            this.Logger.Log(String.Format("Visiting an expression of type(s) {0}.", this.TypeBuilder(valueOfTypedReferenceExpression)));
        }

        public void ProcessExpression(ITypeOfTypedReferenceExpression typeOfTypedReferenceExpression)
        {
            this.Logger.Log(String.Format("Visiting an expression of type(s) {0}.", this.TypeBuilder(typeOfTypedReferenceExpression)));
        }

        public void ProcessExpression(ITypeReferenceExpression typeReferenceExpression)
        {
            this.Logger.Log(String.Format("Visiting an expression of type(s) {0}.", this.TypeBuilder(typeReferenceExpression)));
        }

        public void ProcessExpression(ITypeReferenceExpression typeReferenceExpression, CallChain callChain)
        {
            if (!this.Graph.FromStatics.ContainsKey(typeReferenceExpression))
            {
                this.Graph.FromStatics.Add(typeReferenceExpression, new CallsFromStatic(typeReferenceExpression));
            }
            this.Graph.FromStatics[typeReferenceExpression].CallChains.Add(callChain);
        }

        public void ProcessExpression(IThisReferenceExpression thisReferenceExpression)
        {
            //nothing to do
        }

        public void ProcessExpression(IThisReferenceExpression thisReferenceExpression, PropertyCallChain callChain)
        {
            if (!this.Graph.FromProperties.ContainsKey(callChain.Call))
            {
                this.Graph.FromProperties.Add(callChain.Call, new CallsFromProperty(callChain.Call));
            }
            this.Graph.FromProperties[callChain.Call].CallChains.Add(callChain);
        }

        public void ProcessExpression(IThisReferenceExpression thisReferenceExpression, FieldCallChain callChain)
        {
            if (!this.Graph.FromFields.ContainsKey(callChain.Call))
            {
                this.Graph.FromFields.Add(callChain.Call, new CallsFromField(callChain.Call));
            }
            this.Graph.FromFields[callChain.Call].CallChains.Add(callChain);
        }

        public void ProcessExpression(IThisReferenceExpression thisReferenceExpression, MethodCallChain callChain)
        {
            if (!this.Graph.FromThis.ContainsKey(thisReferenceExpression))
            {
                this.Graph.FromThis.Add(thisReferenceExpression, new CallsFromThis(thisReferenceExpression));
            }
            this.Graph.FromThis[thisReferenceExpression].CallChains.Add(callChain);
        }

        public void ProcessExpression(IThisReferenceExpression thisReferenceExpression, EventCallChain callChain)
        {
            if (!this.Graph.FromThis.ContainsKey(thisReferenceExpression))
            {
                this.Graph.FromThis.Add(thisReferenceExpression, new CallsFromThis(thisReferenceExpression));
            }
            this.Graph.FromThis[thisReferenceExpression].CallChains.Add(callChain);
        }

        public void ProcessExpression(IPropertyReferenceExpression propertyReferenceExpression, CallChain callChain)
        {
            PropertyCallChain Link = new PropertyCallChain(propertyReferenceExpression, callChain);
            this.Accept(propertyReferenceExpression.Target, Link);
        }

        public void ProcessExpression(IPropertyReferenceExpression propertyReferenceExpression, IAssignExpression assignExpression)
        {
            if (!this.Graph.PropertiesAssigned.Contains(propertyReferenceExpression))
                this.Graph.PropertiesAssigned.Add(propertyReferenceExpression);
        }

        public void ProcessExpression(IPropertyReferenceExpression propertyReferenceExpression)
        {
            //nothing to do
        }

        public void ProcessExpression(IMethodReferenceExpression methodReferenceExpression, IMethodInvokeExpression methodInvokeExpression)
        {
            MethodCallChain Link = new MethodCallChain(methodReferenceExpression);
            this.Accept(methodReferenceExpression.Target, Link);
        }

        public void ProcessExpression(IMethodReferenceExpression methodReferenceExpression, CallChain callChain)
        {
            MethodCallChain Link = new MethodCallChain(methodReferenceExpression, callChain);
            this.Accept(methodReferenceExpression.Target, Link);
        }

        public void ProcessExpression(IMethodReferenceExpression methodReferenceExpression)
        {
            this.Logger.Log(String.Format("Visiting an expression of type(s) {0}.", this.TypeBuilder(methodReferenceExpression)));
        }

        public void ProcessExpression(IArgumentReferenceExpression argumentReferenceExpression, CallChain callChain)
        {
            if (!this.Graph.FromArguments.ContainsKey(argumentReferenceExpression))
            {
                this.Graph.FromArguments.Add(argumentReferenceExpression, new CallsFromArgument(argumentReferenceExpression));
            }
            this.Graph.FromArguments[argumentReferenceExpression].CallChains.Add(callChain);
        }

        public void ProcessExpression(IAddressReferenceExpression addressReferenceExpression)
        {
            this.Logger.Log(String.Format("Visiting an expression of type(s) {0}.", this.TypeBuilder(addressReferenceExpression)));
        }

        public void ProcessExpression(IArgumentReferenceExpression argumentReferenceExpression)
        {
            //nothing to do
        }

        public void ProcessExpression(IBaseReferenceExpression baseReferenceExpression)
        {
            //nothing to do
        }

        public void ProcessExpression(IBaseReferenceExpression baseReferenceExpression, CallChain callChain)
        {
            //nothing to do
        }

        public void ProcessExpression(IEventReferenceExpression eventReferenceExpression)
        {
            this.Logger.Log(String.Format("Visiting an expression of type(s) {0}.", this.TypeBuilder(eventReferenceExpression)));
        }

        public void ProcessExpression(IFieldReferenceExpression fieldReferenceExpression, CallChain callChain)
        {
            FieldCallChain Link = new FieldCallChain(fieldReferenceExpression, callChain);
            this.Accept(fieldReferenceExpression.Target, Link);
        }

        public void ProcessExpression(IFieldReferenceExpression fieldReferenceExpression)
        {
            //nothing to do
        }

        public void ProcessExpression(ITypedReferenceCreateExpression typedReferenceCreateExpression)
        {
            this.Logger.Log(String.Format("Visiting an expression of type(s) {0}.", this.TypeBuilder(typedReferenceCreateExpression)));
        }

#endregion

#region Helpers

        public string TypeBuilder(Object subject)
        {
            return String.Format("{0} {1}", this.ClassBuilder(subject), this.InterfaceBuilder(subject));
        }

        public string InterfaceBuilder(Object subject)
        {
            StringBuilder Builder = new StringBuilder();
            foreach (Type Interface in subject.GetType().GetInterfaces())
            {
                Builder.Append(Interface.Name);
                Builder.Append(" ");
            }
            return Builder.ToString();
        }

        public String ClassBuilder(Object subject)
        {
            if (subject.GetType() == typeof(Object))
                return string.Empty;
            if (subject.GetType().BaseType == typeof(Object))
                return subject.GetType().Name;
            return String.Format("{0} {1}", subject.GetType().Name, this.BaseBuilder(subject.GetType().BaseType, String.Empty));
        }

        public String BaseBuilder(Type subject, String built)
        {
            if (subject.BaseType == typeof(Object))
                return subject.Name;
            return String.Format("{0} {1}", subject.Name, this.BaseBuilder(subject.BaseType, built));
        }

#endregion

    }
}
